using System;
using System.IO;
using System.Collections;
using System.Xml;
using System.Diagnostics;
using System.Management;

namespace RunJob
{
	/// <summary>
	/// 
	/// </summary>
	class TaskList
	{
		private const string TI_LABEL="label";
		private const string TI_TASK="task";
		private const string TI_MANAME="maname";
		private const string TI_PROFILE="profile";
		private const string TI_PARAMS="params";
		private const string TI_ACTION="action";
		private const string TI_COMMAND="command";
		private const string TI_FLAG="flag";
		private const string TI_TEXT="text";

		private string LEGAL_TASKS = "|RUN|VERIFY|COMMAND|SET|UNSET|JUMPTO|TEST|ECHO|NOOP|END|ABORT|".ToLower();
		private string LEGAL_ACTIONS = "|ABORT|CONTINUE|SET|UNSET|ECHO|".ToLower();
		private string VERIFY_ITEMS = "numcsobjects|stage-no-change|stage-add|stage-update|stage-rename|stage-delete|stage-failure|disconnector-filtered|disconnector-projected-no-flow|disconnector-projected-flow|disconnector-projected-remove-mv|disconnector-joined-no-flow|disconnector-joined-flow|disconnector-joined-remove-mv|disconnector-remains|connector-flow-remove-mv|connector-filtered-leave-mv|connector-filtered-remove-mv|connector-delete-leave-mv|connector-delete-remove-mv|connector-delete-add-processed|flow-failure|provisioned-add-no-flow|provisioned-add-flow|provisioned-rename-flow|provisioned-rename-no-flow|provisioned-disconnect|provisioned-delete-add-no-flow|provisioned-delete-add-flow|export-add|export-update|export-delete|export-delete-add|export-failure";
		private string[] VERIFY_ELEMENTS;

		private string job_name = "";
		private bool ready = false;
		ArrayList tasks;
		int active_task;
		Hashtable label_index;
		Hashtable flags;

		public TaskList()
		{
			tasks = new ArrayList();
			label_index = new Hashtable();
			flags = new Hashtable();

			ready = false;

			VERIFY_ELEMENTS = VERIFY_ITEMS.ToLower().Split("|".ToCharArray());
		}

		public bool loadFromXML(XmlDocument jobxml)
		{
			int task_index = 0;

			System.Xml.XmlNode task_node = jobxml.GetElementsByTagName("Task").Item(0);
			job_name = task_node.Attributes.GetNamedItem("name").Value;

			System.Xml.XmlNodeList xml_task_list = jobxml.GetElementsByTagName("step");

			foreach (System.Xml.XmlNode task_item in xml_task_list)
			{
				Hashtable task = new Hashtable();
				System.Xml.XmlAttributeCollection task_details = task_item.Attributes;
				foreach (System.Xml.XmlAttribute details in task_details)
				{
					switch (details.Name.ToLower())
					{
						//----------------------------------------------------------------------
						//-- Record the presence of a named item
						case TI_LABEL:
							//-- Store the index
							label_index[details.Value] = task_index;
							break;
						//----------------------------------------------------------------------
						case TI_TASK:
							string this_task = details.Value.ToLower();
							if (LEGAL_TASKS.IndexOf(string.Format("|{0}|",this_task))==-1)
							{
								Console.WriteLine(string.Format("Unknown Task {0} at task element {1}",this_task,task_index));
								return false;
							}
							break;				

						//----------------------------------------------------------------------
						case TI_ACTION:
							break;

						//----------------------------------------------------------------------
						//-- Following items are valid, but do not have checkable values or actions required
						case TI_MANAME:			//-- Could be extended to verify MA is present
						case TI_PROFILE:		//-- Could be extended to verify that Profile is present
						case TI_PARAMS: 
						case TI_COMMAND:
						case TI_FLAG:
						case TI_TEXT:
							break;
						//----------------------------------------------------------------------
						//-- last attempt to verify that a task element is correct
						default:
							string final_test = details.Value.ToLower();
							if (string.Format("|{0}|",VERIFY_ITEMS).IndexOf(string.Format("|{0}|",final_test))!=-1)
							{
								//-- final resort - no task element can be located, throw an error
								Console.WriteLine(string.Format("Unknown element {0} at task element {1}",details.Name,task_index));
								return false;
							}
							break;
					}

					//-- Store the value into the task Hashtable
					task[details.Name.ToLower()] = details.Value;


				}
				//-- and add the task to the list
				tasks.Add(task);
				//-- Increment the task index
				task_index++;
			}

			return true;
		}

		public bool execute()
		{
			active_task = 0;			//-- always start at the beginning
			int max_tasks = tasks.Count;

			//-- Iterate through until we're done
			while (active_task<max_tasks)
			{
				//-- Retrieve the next task
				Hashtable task = (Hashtable)tasks[active_task++];

				switch (task[TI_TASK].ToString().ToLower())
				{
					//------------------------------------------------------------
					//-- execute a MIIS run profile
					case "run":
						if (!executeRunProfile(task))
							actionOnFail(task);
						break;

					//------------------------------------------------------------
					//-- execute an external command
					case "command":
						if (!executeCommandLine(task))
							actionOnFail(task);
						break;

					//------------------------------------------------------------
					//-- verify a MA run profile operation
					case "verify":
						if (!verifyRunProfile(task))
							actionOnFail(task);
						break;
                            
					//------------------------------------------------------------
					//-- set a flag to set
					case "set":
						if (task.ContainsKey(TI_FLAG))
							setFlag(task[TI_FLAG].ToString(),true);
						break;

					//------------------------------------------------------------
					//-- jumps to the selected label
					case "jumpto":
						actionOnFail(task);
						break;

					//------------------------------------------------------------
					//-- set a flag to unset
					case "unset":
						if (task.ContainsKey(TI_FLAG))
							setFlag(task[TI_FLAG].ToString(),false);
						break;

					//------------------------------------------------------------
					//-- verify whether a particular flag is set
					case "test":
						if (task.ContainsKey(TI_FLAG))
						{
							bool this_flag = getFlag(task[TI_FLAG].ToString());
							if (!this_flag)
								actionOnFail(task);
						}
						break;

					//------------------------------------------------------------
					//-- print text to the std console
					case "echo":
						if (task.ContainsKey(TI_TEXT))
							Console.WriteLine(task[TI_TEXT]);
						break;

					//------------------------------------------------------------
					//-- A general do nothing operation (just in case)
					case "noop":
						break;

					//------------------------------------------------------------
					//-- end the run cycle (no error)
					case "end":
						if (task.ContainsKey(TI_TEXT))
							Console.WriteLine(task[TI_TEXT]);
						active_task+=max_tasks;
						break;

					//------------------------------------------------------------
					//-- end the run cycle with an error
					case "abort":
						if (task.ContainsKey(TI_TEXT))
							Console.WriteLine(task[TI_TEXT]);
						doAbort(task);
						break;

					//------------------------------------------------------------
					//--
					default:
						break;
				}
			}

			return true;
		}

		/// <summary>
		/// Process "failure" conditions
		/// </summary>
		/// <param name="task"></param>
		public void actionOnFail(Hashtable task)
		{
			//-- Don't do anything if there isn't an "action on fail" set
			if (!task.ContainsKey(TI_ACTION))
				return;

			string this_action = task[TI_ACTION].ToString().ToLower();

			switch (this_action)
			{
				//------------------------------------------------------------
				//-- Continue regardless (do nothing)
				case "continue":
					break;
				//------------------------------------------------------------
				//-- Halt Operation
				case "abort":
					doAbort(task);
					break;
				
				//------------------------------------------------------------
				//--
				case "set":
					if (task.ContainsKey(TI_FLAG))
						setFlag(task[TI_FLAG].ToString(),true);
					break;
				//------------------------------------------------------------
				//--
				case "unset":
					if (task.ContainsKey(TI_FLAG))
						setFlag(task[TI_FLAG].ToString(),false);
					break;

				//------------------------------------------------------------
				//--
				default:
					//-- Check to see if the task really points towards a label...
					if (label_index.ContainsKey(this_action))
					{
						//-- It does - grab the index
						active_task = (int)label_index[this_action];
					}
					else
					{
						Console.WriteLine(string.Format("Unable to locate label {0} at task item {1}",this_action,active_task));
					}
					break;
			}
		}

		public void doAbort(Hashtable task)
		{
			EventLog.WriteEntry("RunJob",string.Format("Operation on RunJob {0} aborted at task item {1}",job_name,active_task),EventLogEntryType.Error);
			Console.WriteLine(string.Format("Operation on RunJob {0} aborted at task item {1}",job_name,active_task));
			System.Environment.Exit(-1);				
		}

		public bool executeRunProfile(Hashtable task)
		{
			//-- Make sure we've got the minimum parameters
			if (!task.ContainsKey(TI_MANAME) ||
				!task.ContainsKey(TI_PROFILE))
				return false;

			string ma_name = task[TI_MANAME].ToString();
			string profile = task[TI_PROFILE].ToString();

			try
			{
				string ma_filter			= string.Format("name='{0}'",ma_name);
				ManagementScope wmi_scope	= new ManagementScope("root\\MicrosoftIdentityIntegrationServer");
				SelectQuery wmi_query		= new SelectQuery("MIIS_ManagementAgent",ma_filter);
				ManagementObjectSearcher ma = new ManagementObjectSearcher(wmi_scope,wmi_query);

				if (ma.Get().Count==0)
				{
					Console.WriteLine(string.Format("Unable to location Management Agent \"{0}\"",ma_name));
					return false;
				}

				foreach (ManagementObject mo in ma.Get())
				{
					string run_profile = (string)mo.InvokeMethod("Execute",new object[1] {profile});
					if (string.Compare(run_profile,"success",true)!=0)
						return false;
				}

			}
			catch (Exception ex)
			{
				return false;
			}

			return true;
		}

		public bool executeCommandLine(Hashtable task)
		{
			//-- Make sure we've got the minimum parameters
			if (!task.ContainsKey(TI_COMMAND))
				return false;

			string cmdline = task[TI_COMMAND].ToString();
			string parameters = (task.ContainsKey(TI_PARAMS)?task[TI_PARAMS].ToString():"");

			System.Diagnostics.Process cmd = new Process();
			cmd.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
			cmd.StartInfo.FileName = cmdline;
			cmd.StartInfo.Arguments = parameters;

			try
			{
				cmd.Start();
				cmd.WaitForExit();
				
			}
			catch (Exception ex)
			{
				return false;
			}
			
			return true;
		}

		public bool verifyRunProfile(Hashtable task)
		{
			Hashtable stats = getRunHistory(task);
			if (stats==null)	return false;

			string numcsobjects = (stats.ContainsKey("numcsobjects")?stats["numcsobjects"].ToString():"");
			int numcs = 0;
			int ratio = 0;
			if (numcsobjects!="")
				numcs = int.Parse(numcsobjects);

			foreach (string test_stat in VERIFY_ELEMENTS)
			{
				if (task.ContainsKey(test_stat))
				{
					string test = task[test_stat].ToString().Trim();
					string test_op = "=";
					string test_value = stats[test_stat].ToString();
					int is_value=0;
					if (test_value!="" && test_value!=null)
					{
						is_value = int.Parse(test_value);
					}

					if (test.StartsWith(">=") || test.StartsWith("<=") || test.StartsWith(">%") || test.StartsWith("-=") || test.StartsWith("+=") || test.StartsWith("+%"))
					{
						test_op = test.Substring(0,2);
						test = test.Remove(0,2);
					}	
					else if (test.StartsWith("=") || test.StartsWith("%") || test.StartsWith(">") || test.StartsWith("<") || test.StartsWith("+") || test.StartsWith("-"))
					{
						test_op = test.Substring(0,1);
						test = test.Remove(0,1);
					}

					try
					{
						int test_val = int.Parse(test);

						switch (test_op)
						{
							case "=":		if (is_value!=test_val)	return false;	break;

                            case "+":
							case ">":		if (is_value>=test_val)	return false;	break;

                            case "-":
							case "<":		if (is_value<=test_val)	return false;	break;

                            case "+=":
							case ">=":		if (is_value>test_val)	return false;	break;

                            case "-=":
							case "<=":		if (is_value<test_val)	return false;	break;

                            case "+%":
							case ">%":														
								if (numcsobjects=="")	return false;
								ratio = 100 * is_value / numcs;

								if (ratio<=test_val)	return false;

								break;
                            case "%-":
							case "%<":
                            case "<%":
                            case "-%":
							case "%":
																
								if (numcsobjects=="")	return false;															
								ratio = 100 * is_value / numcs;

								if (ratio>test_val)	return false;
								break;

							default:
								Console.WriteLine("Unexpected verify condition!");
								return false;

						}
					}
					catch (Exception ex)
					{
						Console.WriteLine(ex.Message);
						return false;
					}
				}
			}			

			return true;
		}

		public Hashtable getRunHistory(Hashtable task)
		{
			string ma_name = (task.ContainsKey(TI_MANAME)?task[TI_MANAME].ToString():"");
			if (ma_name.Equals(string.Empty))
				return null;

			ManagementScope scope	= new ManagementScope("root\\MicrosoftIdentityIntegrationServer");
			string query			= "MIIS_ManagementAgent";
			string filter			= string.Format("name='{0}'",ma_name);

			SelectQuery WmiQuery = new SelectQuery( query, filter );
			ManagementObjectSearcher searcher = new ManagementObjectSearcher( scope, WmiQuery );
			
			foreach (ManagementObject mo in searcher.Get())
			{
				System.Xml.XmlDocument run_history_xml = new XmlDocument();
				Hashtable rv = new Hashtable();
				string run_history = (string)mo.InvokeMethod("RunDetails",new object[]{});
				run_history_xml.LoadXml(run_history);

				foreach (string stat_name in VERIFY_ELEMENTS)
				{
					string stat_value = getXMLText(run_history_xml,stat_name);
					if (stat_value!=string.Empty)
						rv[stat_name] = stat_value;
				}

				//-- Now grab the CS stats

				string cs_stats = (string)mo.InvokeMethod("NumCSObjects",new object[] {});
				rv["numcsobjects"] = cs_stats;

				return rv;
			}

			return null;
		}

		public string getXMLText(XmlDocument doc,string node_name)
		{
			XmlNodeList gxtl = doc.GetElementsByTagName(node_name);
			foreach (XmlNode gxt in gxtl)
			{
				return gxt.InnerText;
			}
			return "";
		}

		public bool getFlag(string flag_name)
		{
			if (flags.ContainsKey(flag_name))
				return (bool)flags[flag_name];
			return false;
		}

		public void setFlag(string flag_name,bool flag_state)
		{
			flags[flag_name] = flag_state;
		}

		public bool isReady()
		{
			return ready;
		}
	}
}
